﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace VisualProject
{
    //自定义Canvas
    class DrawingCanvas:Canvas
    {
        //存放小方格的容器(UIElement继承Visual,所以所有控件都能存放，但这里我们的需求就只是存放矩形)   
        private List<Visual> visuals = new List<Visual>();

        //重写Canvas的VisualChidrenCount属性,Canvas子元素的个数,我们只在Canvas中添加矩形，所以有多少个矩形就有多少个子元素
        protected override int VisualChildrenCount
        {
            get
            {
                return visuals.Count;
            }
        }
        //重写Canvas的GetVisualChild()方法,index是索引,重写了Canvas获取子元素的方法,因为我们的子元素都在visuals里，所以根据索引返回visuals中的元素即可
        protected override Visual GetVisualChild(int index)
        {
            return visuals[index];
        }

        //添加子元素
        public void AddVisual(Visual visual)
        {
            //添加到visuals中
            visuals.Add(visual);
            //添加到逻辑树
            AddLogicalChild(visual);
            //添加到视觉树
            AddVisualChild(visual);

        }

        //根据坐标找到小方块
        public DrawingVisual GetVisual(Point point)//注意Point结构体是Windows命名控件下的,不是Drawing命名空间的
        {
            //进行命中测试,参考坐标系是this,我们会通过DrawingSurface实例调用这个方法，所以这里的this是DrawingSurface这个实例对象
            HitTestResult hitResult = VisualTreeHelper.HitTest(this, point);
            //如果命中了,那么命中对象hitResult.VisualHit就是我们之前DrawingSurface.AddVisual(visual)传过来的DrawingVisual实例化对象
            //所以这里就把他转成DrawingVisual,为什么要若转呢，因为我们可能没有命中，那么hitResult.VisualHit就是NULL,那么若转后就是NULL没什么影响
            //猜测:之前把在AddVisual方法中，把传过来的DrawingVisual实例添加到了逻辑树和视觉树中，所以这里VisualTreeHelper能进行命中测试，如果
            //没有添加到逻辑树和视觉树，这里就不会命中成功(我的猜测经过豆包验证，是正确的)
            return hitResult.VisualHit as DrawingVisual;
        }

        //删除小方块
        public void DeleteVisual(Visual visual)
        {
            //在visuals中删除
            visuals.Remove(visual);
            //在逻辑树中删除
            RemoveLogicalChild(visual);
            //在视觉树中删除
            RemoveVisualChild(visual);
        }

        //保存命中测试命中的对象
        List<DrawingVisual> hits = new List<DrawingVisual>();

        //获取框选的小方格
        public List<DrawingVisual> GetVisuals(Geometry geometry)
        {
            //清理上一次框选命中的方格
            hits.Clear();
            //命中测试回调，由他来进行命中测试的细节和命中元素的返回
            HitTestResultCallback hitResultCallback = new HitTestResultCallback(HitTestCallBack);
            //命中测试参数，把传过来的geometry作为命中测试参数，外部调用时会把和多选框一样的矩形作为参数传到geometry中
            //然后命中测试，就会根据这个矩形来进行命中测试，矩形内部的元素会被命中，具体命中细节在HitTestCallBack中
            //参数geometry(即外部传过来的矩形)会被作为命中结果中的一个属性
            GeometryHitTestParameters parameters = new GeometryHitTestParameters(geometry);
            VisualTreeHelper.HitTest(this, null, hitResultCallback, parameters);
            //返回框选命中的方格
            return hits;
        }

        //命中测试回调，由他来进行命中测试的细节和命中元素的返回
        private HitTestResultBehavior HitTestCallBack(HitTestResult result)
        {
        //geometryResult.Geometry：就是你传入 parameters 的那个原始几何形状geometry
        //geometryResult.VisualHit：当前被检测到的 Visual（比如你的 DrawingVisual 方格）；
            
            GeometryHitTestResult geometryResult = (GeometryHitTestResult)result;
            DrawingVisual visual = result.VisualHit as DrawingVisual;
            //geometryResult.IntersectionDetail：关键！当前 Visual 与 geometry 的相交程度（比如你代码里判断的 FullyInside，表示几何形状完全包含这个 Visual）。
            if (visual!=null && geometryResult.IntersectionDetail == IntersectionDetail.FullyInside)//命中了并且小方块完全在geometryResult.Geometry内部
            {
                //将命中的小方格添加到命中测试结果中
                hits.Add(visual);
            }
            //命中了后，继续命中测试,意思就是说每次只命中一个
            return HitTestResultBehavior.Continue;
        }
    }
}
